package org.semanticflash.rdf.graph.index
{
	
	
	import com.bigflexlib.ds.DataStructureEvent;
	import com.bigflexlib.ds.container.ContainerDiff;
	import com.bigflexlib.ds.container.utils.ContainerUtil;
	import com.bigflexlib.ds.container.zet.IOSet;
	import com.bigflexlib.ds.container.zet.IOWSet;
	import com.bigflexlib.ds.container.zet.IRSet;
	import com.bigflexlib.ds.container.zet.IWSet;
	import com.bigflexlib.ds.container.zet.impl.owSet;
	import com.bigflexlib.ds.op.SetFilterOp;
	import com.bigflexlib.utils.IOperator;
	
	import flash.utils.Dictionary;
	
	import org.semanticflash.rdf.triple.IRDFIndexedTripleProvider;
	import org.semanticflash.rdf.triple.RDFTriple;
	
	
	


	/**
	 * 
	 * Wraps and transforms a common set of triples into an IRDFIndexedTripleProvider.
	 * This implementation is:
	 * * non-lazy ( indexes as data arrives )
	 * * uses sets
	 * 
	 * It is very memory intensive.
	 * TODO: design a lazy index strategy that returns a consumed-aware IOSet
	 * 
	 * @author aldo.bucchi@gmail.com
	 * 
	 */	
	public class RDFTripleIndex implements IRDFIndexedTripleProvider
	{
		
		
		
		
		
		// we need to keep a reference to the ops....
		private var filterOps:Dictionary = new Dictionary( true );		
		
		
		
		
		
		private var indexSpecs:Array = [
				[ 1, 0, 0 ],
				[ 0, 1, 0 ],
				[ 0, 0, 1 ],
				[ 0, 0, 0 ],
				[ 1, 1, 0 ],
				[ 0, 1, 1 ]
			];
		
		
		
		/**
		 * 
		 * The triple set over which this index operates
		 * 
		*/		
		private var tripleSet:IOSet;

		public function get triples( ):IOSet
		{
			return tripleSet;
		}


		/**
		 * 
		 * Entries are sets corresponding to each of the computed masks
		 * they are the real indexes
		 * 
		*/
		private var indexedSets:Dictionary = new Dictionary( );



		/**
		 * 
		 * Views operate over indexed sets
		 * 
		*/
		private var views:Dictionary = new Dictionary( );



		
		
		/**
		 * 
		 * 
		 * 
		 * 
		 * @param tripleSet
		 * @param indexSpecs
		 * 
		 */		
		public function RDFTripleIndex( tripleSet:IOSet, indexSpecs:Array = null )
		{

			this.tripleSet = tripleSet;	
			
			if ( indexSpecs != null )
			{
				this.indexSpecs = indexSpecs;				
			}
			
			
			tripleSet.addEventListener( DataStructureEvent.CHANGE, tripleSet_change );

		}



		
		
		/**
		 * 
		 * Returns an observable set over the triples that match the specified mask.
		 * 
		 * @param mask
		 * @return 
		 * 
		 */		
		public function getTriples( mask:RDFTriple ):IOSet
		{
		
			if ( ! views[ mask ] )
			{
				
				// find out the indexedMasks that contain this mask
				var indexMasks:IRSet = mask.getMasks( indexSpecs );
				
				if ( indexMasks.contains( mask ) ) // special case. this triple mask is indexed. we directly return the existing set
				{
					var indexedSet:IOSet = getIndexedSetForMask( mask );
					views[ mask ] = indexedSet;
					return indexedSet;
				}
				
				// keep only the most specific ones
				indexMasks = RDFTriple.keepMostSpecific( indexMasks );

				// and from these, choose the smallest set ( this becomes relevant when we scale up )
				var zet:IOSet = chooseSmallestIndexedSet( indexMasks );
				
				// final step: wrap it in a filter
				var filterFunc:Function = function( e:* ):Boolean
					{
						return mask.contains( e as RDFTriple );
					}
					
				
				var filteredSet:IOWSet = owSet( );	
					
				var op:IOperator = new SetFilterOp( zet, filteredSet, filterFunc );
				
				filterOps[ filteredSet ] = op; // we store a copy so it doesn't become garbage collected
				
				// zet = new FilteredSetWrapper( zet, filterFunc );

				
				// and put it in the cache
				views[ mask ] = filteredSet;

			}
		
			return views[ mask ] as IOSet;
		}




		
		
		
		
		/**
		 * 
		 * Looks at the available indexed sets for a set of masks and returns the smallest one.
		 * This is used to speed up the first filtering ( filtering on the smallest is faster ).
		 * 
		 * @param masks
		 * @return 
		 * 
		 */		
		private function chooseSmallestIndexedSet( masks:IRSet ):IOSet
		{
			var smallestSetSoFar:IOSet;
			var masksArr:Array = ContainerUtil.toArray( masks );
			for each ( var mask:RDFTriple in masksArr )
			{
				var zet:IOSet = getIndexedSetForMask( mask );
				
				if ( smallestSetSoFar == null || smallestSetSoFar.size > zet.size )
				{
					smallestSetSoFar = zet;
				}			
			}			
			return smallestSetSoFar;
		}
		
		



		/**
		 * 
		 * 
		 */
		private function tripleSet_change( event:DataStructureEvent ):void
		{
		
			autoUpdate( false );
		
		
			var diff:ContainerDiff = event.diff as ContainerDiff;
			diff.walk( function( e:*, m:int ):void {

					mod( e as RDFTriple, m == 1 ); // m can be 1 or -1 ( we are listening to a set remember )

				} );		
		
		
			autoUpdate( true );
		
		}






		/**
		 * 
		 * TODO:
		 * we should only disable those sets that are being modified
		 * ( hook some checking on the mod() method )
		 * 
		 * @param enable
		 * 
		 */
		private function autoUpdate( enable:Boolean ):void
		{
		
			for ( var key:* in indexedSets )
			{
				var zet:IWSet = indexedSets[ key ] as IWSet;
				
				if ( enable )
					zet.autoUpdateFlag.enable( );
				else
					zet.autoUpdateFlag.disable( );			
			}
		
		}
















		/**
		 * 
		 * Creates on demand if necessary
		 * 
		 * @return 
		 * 
		 */
		private function getIndexedSetForMask( tripleMask:RDFTriple ):IOWSet
		{
			if ( ! indexedSets[ tripleMask ] )
			{
				 indexedSets[ tripleMask ] = owSet( );
			}

			return indexedSets[ tripleMask ] as IOWSet;
		}






		//////////////////////////////////////////////////////////////////
		// add / remove from the index
		//////////////////////////////////////////////////////////////////



		/**
		 * 
		 * Adds a triple to all the relevant indexes
		 * 
		 * @param triple
		 * 
		 */
		private function add( triple:RDFTriple ):void
		{
		
			mod( triple, true );
		
		}



		/**
		 * 
		 * Removes a triple from all the indexes in which it occurs
		 * 
		 * @param triple
		 * 
		 */
		private function remove( triple:RDFTriple ):void
		{
		
			mod( triple, false );
		
		}

		
		
		
		/**
		 * 
		 * Internal two in one function for convenience
		 * 
		 * @param triple
		 * @param add
		 * 
		 */		
		private function mod( triple:RDFTriple, add:Boolean ):void
		{
		
			var masks:Array = ContainerUtil.toArray( triple.getMasks( indexSpecs ) );
			
			for each ( var mask:RDFTriple in masks )
			{
				var indexSet:IOWSet = getIndexedSetForMask( mask );
				
				if ( add )
					indexSet.add( triple );
				else
					indexSet.remove( triple );						
			}
		
		}



		
		
	}
	
	
}













